#include <android/log.h>
#include <jni.h>
#include <cstring>
#include <string>

extern "C" {
#include "aes.h"
#include "array.h"
#include "aes_cbc.h"
#include "zbase64.h"
#include "MD5.h"
}

#define TAG "scanner_c"
#define LogE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)

enum {
    ERR_GET_ENV = -1,
    ERR_VERIFY_PKG_NAME = -2,
    ERR_VERIFY_PKG_SIGN = -3,
    ERR_REGISTER_METHOD = -4
};

static void throwIllegalStateException(JNIEnv *env, const char *message) {
    env->ThrowNew(env->FindClass("java/lang/IllegalStateException"), message);
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
#if defined(VERIFY_PKG_NAME) && defined(VERIFY_PKG_SIGN)
    JNIEnv *env;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return ERR_GET_ENV;
    }

    jclass activity_thread_class = env->FindClass("android/app/ActivityThread");
    jmethodID currentApplication = env->GetStaticMethodID(activity_thread_class,
                                                          "currentApplication",
                                                          "()Landroid/app/Application;");
    jobject application = env->CallStaticObjectMethod(activity_thread_class, currentApplication);

    jclass application_class = env->GetObjectClass(application);
    jmethodID getPackageName = env->GetMethodID(application_class, "getPackageName",
                                                "()Ljava/lang/String;");
    jstring pkgName = (jstring) env->CallObjectMethod(application, getPackageName);
    const char *_pkgName = env->GetStringUTFChars(pkgName, nullptr);
    if (strcmp(_pkgName, VERIFY_PKG_NAME) != 0) {
        return ERR_VERIFY_PKG_NAME;
    }

    jmethodID getPackageManager = env->GetMethodID(application_class, "getPackageManager",
                                                   "()Landroid/content/pm/PackageManager;");
    jobject packageManager = env->CallObjectMethod(application, getPackageManager);
    jclass packageManager_class = env->GetObjectClass(packageManager);
    jmethodID methodID_getPackageInfo = env->GetMethodID(packageManager_class, "getPackageInfo",
                                                         "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jobject packageInfo = env->CallObjectMethod(packageManager, methodID_getPackageInfo, pkgName,
                                                64);

    jclass packageInfo_class = env->GetObjectClass(packageInfo);
    jfieldID signatures = env->GetFieldID(packageInfo_class, "signatures",
                                          "[Landroid/content/pm/Signature;");
    jobjectArray signature_arr = (jobjectArray) env->GetObjectField(packageInfo, signatures);
    jobject signature = env->GetObjectArrayElement(signature_arr, 0);

    jclass signature_class = env->GetObjectClass(signature);
    jmethodID methodID_toByteArray = env->GetMethodID(signature_class, "toByteArray", "()[B");
    jbyteArray signature_array = (jbyteArray) env->CallObjectMethod(signature,
                                                                    methodID_toByteArray);

    jsize len = env->GetArrayLength(signature_array);
    jbyte *arrayElements = env->GetByteArrayElements(signature_array, JNI_FALSE);
    const std::string originStr((char *) arrayElements, len);
    env->ReleaseByteArrayElements(signature_array, arrayElements, 0);


    MD5 md5 = MD5(originStr);
    std::string md5Result = md5.hexdigest();
    const char *c_str = md5Result.c_str();

    if (strcmp(c_str, VERIFY_PKG_SIGN) != 0) {
        return ERR_VERIFY_PKG_SIGN;
    }

    return JNI_VERSION_1_6;
#else
    return JNI_ERR;
#endif
}

#define AES_KEY  "d7a6e2f1c3b9a2b5e8d1a4c3e6f7b8a9"
#define AES_IV "b3c1d4e5f6a7b8c9"

extern "C"
JNIEXPORT jbyteArray JNICALL
Java_com_tools_scanner_utils_NativeUtil_00024Companion_encrypt(JNIEnv *env, jobject thiz,
                                                               jstring nonce, jstring timestamp) {
    const char *c_nonce = env->GetStringUTFChars(nonce, JNI_FALSE);
    const char *c_timestamp = env->GetStringUTFChars(timestamp, JNI_FALSE);

    size_t total_length = strlen(AES_KEY) + strlen(c_nonce) + strlen(c_timestamp) + 1;
    char *content = (char *) malloc(total_length);
    if (content == nullptr) {
        env->ReleaseStringUTFChars(nonce, c_nonce);
        return nullptr;
    }

    snprintf(content, total_length, "%s%s%s", AES_KEY, c_nonce, c_timestamp);

    LogE("time=>%s", c_timestamp);
    LogE("content=>%s", content);

    ByteArray aesKey;
    aesKey.value = (uint8_t *) AES_KEY;
    aesKey.len = strlen(AES_KEY);

    uint8_t *iv = (uint8_t *) AES_IV;

    ByteArray plain;
    plain.value = (uint8_t *) content;
    plain.len = strlen(content);

    uint8_t out_buffer[BUFFER_SIZE];
    ByteArray result = aes_cbc_encrypt(&aesKey, iv, &plain, out_buffer);

    free(content);
    env->ReleaseStringUTFChars(nonce, c_nonce);
    env->ReleaseStringUTFChars(timestamp, c_timestamp);

    if (result.value != nullptr) {
        jbyteArray output = env->NewByteArray(result.len);
        env->SetByteArrayRegion(output, 0, result.len, (jbyte *) result.value);
        return output;
    } else {
        if (result.len == 0) {
            throwIllegalStateException(env, "Encryption failed");
        } else {
            throwIllegalStateException(env, "Bad padding");
        }
        return nullptr;
    }
}